package net.xinqushi.design.templateMethod.pool;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import net.xinqushi.design.templateMethod.core.DefaultSetting;

/**
 *   较为简单的Socket连接池源码，比对学习
 * @param <T>
 */
public class GenericConnectionPool<T> implements ConnectionPool<T> {

	public static final int DEFAULT_MAX_ACTIVE  = DefaultSetting.POOL_MAX_ACTIVE;

	public static final int DEFAULT_MAX_IDLE  = DefaultSetting.POOL_MAX_IDLE;

	public static final long DEFAULT_MAX_BLOCKING_TIME= DefaultSetting.POOL_GET_BLOCKING_TIME;

	public static final long DEFAULT_MAX_IDLE_TIME = DefaultSetting.POOL_MAX_IDLE_TIME;

	protected PoolableConnectionFactory<T> factory;

	protected BlockingQueue<T> connectionPool;

	protected volatile int maxActive;

	protected volatile long maxBlockingTime;

	protected volatile long  keepIdleTime;

	protected AtomicInteger idleNumber;

	protected AtomicInteger activeNumber;

	protected volatile boolean isClosed;

	protected final ReentrantLock mainLock=new ReentrantLock();

	public GenericConnectionPool(PoolableConnectionFactory<T> factory){
		this(factory,DEFAULT_MAX_ACTIVE,DEFAULT_MAX_BLOCKING_TIME,DEFAULT_MAX_IDLE_TIME);
	}

	public GenericConnectionPool(PoolableConnectionFactory<T> factory,int maxActive){
		this(factory,maxActive,DEFAULT_MAX_BLOCKING_TIME,DEFAULT_MAX_IDLE_TIME);
	}

	public GenericConnectionPool(PoolableConnectionFactory<T> factory,int maxActive,long maxBlockingTime){
		this(factory,maxActive,maxBlockingTime,DEFAULT_MAX_IDLE_TIME);
	}

	public GenericConnectionPool(PoolableConnectionFactory<T> factory,
			int maxActive,long maxBlockingTime,long keepIdleTime){
		this(factory,new LinkedBlockingQueue<T>(),maxActive,maxBlockingTime,keepIdleTime);
	}

	public GenericConnectionPool(PoolableConnectionFactory<T> factory,BlockingQueue<T> connectionPool,
			int maxActive,long maxBlockingTime,long keepIdleTime){
		if (maxActive < 0)
			throw new IllegalArgumentException();
		if (factory == null || connectionPool == null)
			throw new NullPointerException();
		this.factory=factory;
		this.connectionPool=connectionPool;
		this.maxActive=maxActive;
		this.maxBlockingTime=maxBlockingTime;
		this.keepIdleTime=keepIdleTime;
		this.idleNumber = new AtomicInteger();
		this.activeNumber= new AtomicInteger();
	}

	public T getConnection() throws Exception {
		if (isClosed) {
			throw new Exception("ConnectionPool is already closed.");
		}
		if (idleNumber.get() == 0) {
			addConnection();
		}
		T connection=null;
		if(maxBlockingTime>0){
			connection=connectionPool.poll(maxBlockingTime, TimeUnit.MILLISECONDS);
		}else{
			connection=connectionPool.take();
		}
		try{
			//			if (!factory.validateConnection(connection)) {
			//				factory.activateConnection(connection);
			//			}
			idleNumber.decrementAndGet();
			activeNumber.incrementAndGet();
		}catch(Exception e){
			factory.destroyConnection(connection);
			idleNumber.decrementAndGet();
			throw new Exception("Get Connection Failed!");
		}
		return connection;
	}

	public void returnConnection(T obj) throws Exception {
		if (isClosed) {
			try {
				factory.destroyConnection(obj);
			} catch(Exception e) {
				// ignore error, keep destroying the rest
			}
			return ;
		}
		mainLock.lock();
		try{
			if(idleNumber.get()+activeNumber.get()> maxActive){
				try{
					factory.destroyConnection(obj);
				}catch(Exception e){
					// ignore				
				}
			}else{
				connectionPool.offer(obj);
				idleNumber.incrementAndGet();
			}
		}finally{
			activeNumber.decrementAndGet();
			mainLock.unlock();
		}
	}

	public void addConnection() throws Exception {
		if (isClosed) {
			throw new Exception("ConnectionPool is already closed.");
		}
		mainLock.lock();
		try{
			//if (idleNumber.get() != 0||activeNumber.get() >= maxActive) {
			if (idleNumber.get()+activeNumber.get() >= maxActive) {
				return;
			}
			T connection=factory.makeConnection();
			if(factory.validateConnection(connection)){
				idleNumber.incrementAndGet();
				connectionPool.offer(connection);
			}
		}finally{
			mainLock.unlock();
		}
	}

	public int getNumIdle() {
		return idleNumber.get();
	}

	public int getNumActive() {
		return activeNumber.get();
	}

	public void clear() throws Exception {
		mainLock.lock();
		try{
			for(T connection:connectionPool){
				try {
					factory.destroyConnection(connection);
				} catch(Exception e) {
					// ignore error, keep destroying the rest
				}
			}
			connectionPool.clear();
		}finally{
			mainLock.unlock();
		}
	}

	public void close() throws Exception {
		if (isClosed) {
			return;
		}
		isClosed = true;
		clear();
	}

	public boolean isClosed() {
		return isClosed;
	}

}
